﻿using System;
using System.Collections.Generic;
using System.Reflection;
using PSE.ProcessManager.Common.Gateway;
using System.IO;
using System.ServiceModel;
using PSE.ProcessManager.Common.Process;
using PSE.ProcessManager.Server.Process.Configuration;
using System.Configuration;
using PSE.Framework.Process;

namespace PSE.ProcessManager.Server.Process
{
    public abstract class BaseProcess<TConfig> : BaseProcess
        where TConfig : ProcessConfigurationSection
    {
        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public BaseProcess()
            : base()
        {
        }

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public BaseProcess(int id, string name)
            : base(id, name)
        {
        }

        #region Configuration
        /// <summary>
        /// Propriedade que armazena a chave de criptografia dos dados armazenados na seção de configuração do processo
        /// Deverá ser atribuída pelas classes base de processo de cada produto
        /// </summary>
        protected byte[] ConfigurationCryptoKey
        {
            get;
            set;
        }

        private object _syncRoot = new object();
        private TConfig _section;
        protected TConfig Configuration
        {
            get
            {
                lock (_syncRoot)
                {
                    if (_section == null)
                    {
                        _section = (TConfig)System.Configuration.ConfigurationManager.GetSection("processConfiguration");
                        if (_section != null)
                        {
                            foreach (ProcessConfigurationElement element in _section.Settings)
                            {
                                element.SetCryptoKey(ConfigurationCryptoKey);
                            }
                        }
                    }
                }

                return _section;
            }
        }
        #endregion
    }

    /// <summary>
    /// Classe base para os processos do Servidor.
    /// </summary>
    public abstract class BaseProcess : AppDomainObject
    {
        #region Properties

        /// <summary>
        /// Gateway de comunicação.
        /// </summary>
        private ReceiverGateway _gateway = new ReceiverGateway();

        /// <summary>
        /// Instancia do processo em execução.
        /// </summary>
        private BaseProcess _process = null;

        /// <summary>
        /// Nome do processo
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Id do Processo
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Identificação do último Client que enviou uma mensagem.
        /// </summary>
        public string LastClientConnected { get; set; }
        /// <summary>
        /// Status do Processo
        /// </summary>
        public ProcessStatus Status { get; set; }

        private string _assemblyFullName;
        public string AssemblyFullName
        {
            get { return _assemblyFullName; }
            set { _assemblyFullName = value; }
        }

        public bool IsAborted { get; private set; }

        #endregion

        /// <summary>
        /// Impedir que o objeto saia da memoria durante a execução
        /// </summary>
        /// <returns></returns>
        public override object InitializeLifetimeService()
        {
            return null;
        }

        #region Constants

        protected const int _intitializedMessageId = 1;
        protected const int _finalizeMessageId = 2;
        protected const int _finalizeOnFailMessageId = 3;
        protected const int _errorMessageId = 4;
        protected const int _statusMessageId = 5;
        protected const int _abortExecutedMessageId = 8;

        protected const string _operationErrorMessage = "ThrowException";
        protected const string _finalizeOperation = "Finalize";
        protected const string _finalizeOnFailOperation = "FinalizeOnFail";

        #endregion

        #region Events

        /// <summary>
        /// Evento disparado na inicialização do Processo.
        /// </summary>
        protected event EventHandler Initialized;

        /// <summary>
        /// Evento disparado no Interrompimento do Processo.
        /// </summary>
        protected event EventHandler Aborted;

        #endregion

        /// <summary>
        /// Construtor da classe
        /// </summary>
        public BaseProcess()
        {
            Status = new ProcessStatus();
            this.OnReceivedMessage += new ServiceEvents.MessageReceived(BaseProcess_OnReceivedMessage);
        }

        /// <summary>
        /// Construtor da classe.
        /// </summary>
        public BaseProcess(int id, string name)
            : this()
        {
            this.Id = id;
            this.Name = name;
        }

        void BaseProcess_OnReceivedMessage(BaseMessage message)
        {
            this.ReceivedMessage(message, this);
        }

        /// <summary>
        /// Método executado no Inicio do Processo;
        /// </summary>
        public void BaseProcess_OnInitialize()
        {
            //Set Callback instance to Receiver Gateway
            _gateway.CallbackInstance = OperationContext.Current.GetCallbackChannel<IListenerServiceCallback>();

            if (Initialized != null)
                Initialized(this, new EventArgs());

            ProcessInitialized();
        }

        #region Evento OnReceivedMessage

        /// <summary>
        /// Evento disparado ao receber uma mensagem.
        /// </summary>
        public event ServiceEvents.MessageReceived OnReceivedMessage;

        #endregion

        #region Evento OnFinalize
        /// <summary>
        /// Evento disparado ao finalizar o processamento.
        /// </summary>
        public event EventHandler Finalized;
        #endregion


        #region SendMessage

        /// <summary>
        /// Envia uma mensagem para o Inteligence Module.
        /// </summary>
        public void SendMessage(BaseMessage message)
        {
            message.To = LastClientConnected;

            if (_gateway != null)
                _gateway.SendMessageCallback(message);
            else
            {
                //TODO: Disparar exception. 
            }
        }

        /// <summary>
        /// Envia uma mensagem para o Inteligence Module.
        /// </summary>
        public void SendMessage<T>(Message<T> message)
        {
            message.To = LastClientConnected;

            if (_gateway != null)
                _gateway.SendMessageCallback<T>(message);
            else
            {
                //TODO: Disparar exception. 
            }
        }

        #endregion


        #region BeginSendMessage
        /// <summary>
        /// Envia uma mensagem para o Inteligence Module.
        /// </summary>
        public void BeginSendMessage(BaseMessage message)
        {
            message.To = LastClientConnected;

            if (_gateway != null)
                _gateway.BeginSendMessageCallback(message);
            else
            {
                //TODO: Disparar exception. 
            }
        }

        /// <summary>
        /// Envia uma mensagem para o Inteligence Module.
        /// </summary>
        public void BeginSendMessage<T>(Message<T> message)
        {
            message.To = LastClientConnected;

            if (_gateway != null)
                _gateway.BeginSendMessageCallback<T>(message);
            else
            {
                //TODO: Disparar exception. 
            }
        }
        #endregion

        public void SendStatus(int progress, string step)
        {
            this.Status.Progress = progress;
            this.Status.Step = step;
            this.SendStatus();
        }

        public void SendStatus(string step)
        {
            this.SendStatus(0, step);
        }

        /// <summary>
        /// Envia o status do processo aos clientes conectados.
        /// </summary>
        public void SendStatus()
        {
            //Cria a mensagem contendo o status do processo.
            Message<ProcessStatus> statusMessage = new Message<ProcessStatus>();

            statusMessage.MessageId = _statusMessageId;
            statusMessage.ProcessId = this.Id;
            statusMessage.Operation = "RetrieveStatus";

            statusMessage.Parameter = new ProcessStatus(this.Status);

            //Envia a mensagem ao Inteligence Manager.
            BeginSendMessage(statusMessage);
        }

        /// <summary>
        /// Inicia o processo.
        /// </summary>
        public void Initialize(string[] args)
        {
            using (ExecutorProcessManager process = ExecutorProcessManager.GetInstance())
            {
                process.Initialize(args[0], new ParentProcessStartInfo(args));

                string processAddress = process.ProcessId.ToString();

                try
                {
                    _gateway.StartListener(processAddress, Protocol.NamedPipe);
                    _gateway.OnReceiveMessage = this.OnReceivedMessage;
                }
                catch (Exception ex)
                {
                    throw new Exception("An exception occours trying to Start Listener Gateway. See inner exception for more details", ex);
                }

                process.WaitForExit();

                if (!this.IsFinalized)
                {
                    MethodInfo method = this.GetType().GetMethod("Stop");
                    if (method != null)
                        method.Invoke(this, null);
                    else
                        this.Abort();
                }
            }
        }

        public abstract string GetUserName();

        /// <summary>
        /// Interrompe a execução do Processo.
        /// </summary>
        public void Abort()
        {
            this.IsAborted = true;
            this.IsFinalized = true;

            BaseMessage message = new BaseMessage();
            message.MessageId = _abortExecutedMessageId;
            message.Operation = "Aborted";
            message.ProcessId = this.Id;
            BeginSendMessage(message);

            if (Aborted != null)
                Aborted(this, new EventArgs());

            if (Finalized != null)
                Finalized(this, new EventArgs());
        }

        /// <summary>
        /// Envia uma mensagem de iniciliazação para o ProcessManager
        /// </summary>
        private void ProcessInitialized()
        {
            BaseMessage message = new BaseMessage();
            message.ProcessId = this.Id;
            message.MessageId = _intitializedMessageId;
            message.Operation = "Initilized";
            SendMessage(message);
        }

        public bool IsFailed { get; private set; }
        public bool IsFinalized { get; private set; }

        /// <summary>
        /// Finaliza o processo.
        /// </summary>
        protected void Finalize()
        {
            Finalize(false);
        }

        /// <summary>
        /// Finaliza o processo.
        /// </summary>
        protected void Finalize(bool fail)
        {
            this.IsFailed = fail;
            this.IsFinalized = true;

            // Só envia mensagem se processo não falhar
            if (!fail)
            {
                BaseMessage message = new BaseMessage();
                message.MessageId = _finalizeMessageId;
                message.Operation = _finalizeOperation;
                message.ProcessId = this.Id;
                BeginSendMessage(message);
            }

            if (Finalized != null)
                Finalized(this, new EventArgs());
        }

        //protected int BeginWriteLog()
        //{
        //    return Logger.Write(Id, GetUserName(), DateTime.Now);
        //}

        //protected void EndWriteLog(int idLog)
        //{
        //    Logger.Write(idLog, DateTime.Now);
        //}

        //protected void EndWriteLog(int idLog, string errorMessage)
        //{
        //    Logger.Write(idLog, DateTime.Now, errorMessage);
        //}

        protected virtual BaseMessage CreateExceptionMessage(Exception ex)
        {
            Message<string> message = new Message<string>();
            message.Parameter = ex.Message;
            return message;
        }

        /// <summary>
        /// Manipula a exceção e, se determinado, finaliza o processo.
        /// </summary>
        /// <param name="ex">Exception a ser manipulada.</param>
        /// <param name="finalize">Indica se deve ou não finalizar o processo.</param>
        protected virtual void HandleException(Exception ex, bool finalize)
        {
            if (ex == null)
                throw new ArgumentNullException("ex");

            BaseMessage message = this.CreateExceptionMessage(ex);

            message.MessageId = _errorMessageId;
            message.Operation = _operationErrorMessage;
            message.ProcessId = this.Id;
            message.To = this.LastClientConnected;

            BeginSendMessage(message);

            if (finalize)
                this.Finalize(true);
        }

        /// <summary>
        /// Manipula a exceção e, se determinado, finaliza o processo.
        /// </summary>
        /// <param name="ex">Exception a ser manipulada.</param>
        protected void HandleException(Exception ex)
        {
            HandleException(ex, false);
        }

        /// <summary>
        /// Executa o método definido na mensagem
        /// </summary>
        /// <param name="message">Mensagem recebida</param>
        /// <param name="process">Instância do processo.</param>
        protected virtual void ReceivedMessage(BaseMessage message, object process)
        {
            try
            {
                _process = (BaseProcess)process;

                LastClientConnected = message.From;

                VerifyAccess(message);

                List<object> parameters = new List<object>();
                object[] parametersObject = null;

                //Define o Id do processo.
                this.Id = message.ProcessId;

                MethodInfo method = process.GetType().GetMethod(message.Operation);
                if (method == null)
                    throw new InvalidOperationException(string.Format("Operation '{0}' not found", message.Operation));

                if (method.GetParameters() != null && method.GetParameters().Length > 0)
                    parametersObject = new object[] { message };

                method.Invoke(process, parametersObject);
            }
            catch (Exception ex)
            {
                if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null)
                {
                    HandleException(ex.InnerException, true);
                }
                else
                {
                    HandleException(ex, true);
                }
            }
        }

        /// <summary>
        /// Verifica se o usuário autenticado tem acesso para executar a operação.
        /// </summary>
        /// <param name="message"></param>
        private void VerifyAccess(BaseMessage message)
        {
            string function = _process.GetType().Namespace + "." + message.Operation;
            //if (!AuthorizationContext.Current.CheckUserAccess(function))
            //if (false)
            //    HandleException(new AccessIsDeniedException("Access is denied"), false);
        }
    }
}
